#include "stdafx.h"
#include "TwoObjectUpdater.h"


static const int kInvalidIndex = -1;
typedef std::unique_lock<std::mutex> UniqueLock;


TwoObjectUpdater::TwoObjectUpdater()
{
	mIndexOfValidObject = kInvalidIndex;
	mTwoObjects[0] = nullptr;
	mTwoObjects[1] = nullptr;
	mShouldStop = false;
	mReleaseFunction = nullptr;
	mReferenceCountForReading[0] = mReferenceCountForReading[1] = 0;
	mFunctionConditions[0] = mFunctionConditions[1] = kIsDiscarded;
	mUpdateCount = mCancelCount = 0;
	mThread = nullptr;
}


TwoObjectUpdater::~TwoObjectUpdater()
{
	StopRunning();
	delete mThread;

	if (mReleaseFunction)
	{
		mReleaseFunction(mTwoObjects[0]);
		mReleaseFunction(mTwoObjects[1]);
	}
}

bool TwoObjectUpdater::IsEmpty() const
{
	return (nullptr == mTwoObjects[0] || nullptr == mTwoObjects[1]);
}

void TwoObjectUpdater::StartRunning(void* inObj1, void* inObj2, FunctionWithObject inReleaseFunction)
{
	mTwoObjects[0] = inObj1;
	mTwoObjects[1] = inObj2;
	mShouldStop = false;
	mReleaseFunction = inReleaseFunction;
	mThread = new std::thread(&TwoObjectUpdater::Run, this);
}

void TwoObjectUpdater::StopRunning()
{
	UniqueLock lock(mMutex);
	mShouldStop = true;
	mCancelCount = mUpdateCount;
	mCondition.notify_one();
	lock.unlock();

	if (nullptr != mThread && mThread->joinable())
	{
		try
		{
			// there may occur exception if mThread is finished already
			mThread->join();
		}
		catch (...)
		{
			if (mThread->joinable())
			{
				// some unexpected exception
				throw;
			}
		}
	}
}

const void* TwoObjectUpdater::BeginRead()
{
	UniqueLock lock(mMutex);

	void* result = nullptr;
	if (0 == mIndexOfValidObject || 1 == mIndexOfValidObject)
	{
		++mReferenceCountForReading[mIndexOfValidObject];
		result = mTwoObjects[mIndexOfValidObject];
	}
	return result;
}
void TwoObjectUpdater::EndRead(const void* inReleasedObject)
{
	UniqueLock lock(mMutex);
	for (int i = 0; i < 2; ++i)
	{
		if (inReleasedObject == mTwoObjects[i])
		{
			int& r = mReferenceCountForReading[i];
			if (0 == --r)
			{
				mCondition.notify_one();
			}
			else if (r < 0)
			{
				r = 0;
			}			
			break;
		}
	}
}

void TwoObjectUpdater::UpdateFreeObject(FunctionWithObject inFunction)
{
	UniqueLock lock(mMutex);
	if (kIsRunning == mFunctionConditions[0])
	{
		mFunctionConditions[1] = kShouldRun;
		mUpdateFunctions[1] = inFunction;
	}
	else
	{
		mFunctionConditions[0] = kShouldRun;
		mUpdateFunctions[0] = inFunction;
		if (kShouldRun == mFunctionConditions[1])
		{
			mFunctionConditions[1] = kIsDiscarded;
		}
	}

	mCancelCount = mUpdateCount;
	mIsInvalidated = false;
	mCondition.notify_one();
}

void TwoObjectUpdater::SetNotifierCallback(FunctionVoid notifier)
{
	UniqueLock lock(mMutex);
	mNotifierFunction = notifier;
}

void TwoObjectUpdater::Invalidate()
{
	UniqueLock lock(mMutex);
	mIsInvalidated = true;
	mIndexOfValidObject = kInvalidIndex;
	mCancelCount = mUpdateCount;
}

bool TwoObjectUpdater::IsCancelled() const
{
	UniqueLock lock(mMutex);
	return (mCancelCount >= mUpdateCount);
}

int TwoObjectUpdater::GetTotalUpdateCount() const
{
	UniqueLock lock(mMutex);
	return mUpdateCount;
}

//------ private ------

void TwoObjectUpdater::Run()
{
	UniqueLock lock(mMutex);

	while (!mShouldStop)
	{
		while (!mIsInvalidated)
		{
			int objectIndex = (0 == mIndexOfValidObject ? 1 : 0);

			if (0 < mReferenceCountForReading[objectIndex])
			{
				if (kInvalidIndex == mIndexOfValidObject &&
					0 == mReferenceCountForReading[1])
				{
					objectIndex = 1;
				}
				else
				{
					// wait until the reading ends...
					break;
				}
			}
			int functionIndex;
			if (kShouldRun == mFunctionConditions[0])
			{
				functionIndex = 0;
			}
			else if (kShouldRun == mFunctionConditions[1])
			{
				functionIndex = 1;
			}
			else
			{
				// wait for fresh function...
				break;
			}
			mFunctionConditions[functionIndex] = kIsRunning;
			++mUpdateCount;

			lock.unlock();
			mUpdateFunctions[functionIndex](mTwoObjects[objectIndex]);
			lock.lock();

			mFunctionConditions[functionIndex] = kIsDiscarded;
			if (mCancelCount < mUpdateCount)
			{
				mIndexOfValidObject = objectIndex;
				if (mNotifierFunction)
				{
					lock.unlock();
					mNotifierFunction();
					lock.lock();
				}
			}
		}
		if (mShouldStop)
		{
			break;
		}
		mCondition.wait(lock);
	}
}
